package com.moyu.gateway.interfaceimpl;

import cn.dev33.satoken.stp.StpInterface;
import cn.hutool.core.collection.CollectionUtil;
import com.moyu.common.core.constants.CacheConstants;
import com.moyu.common.core.utils.RandomTimeUtils;
import com.moyu.common.redis.service.RedisService;
import com.moyu.common.redis.service.RedissonLockService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 自定义权限验证接口扩展,在需要鉴权时自动调用
 *
 * @Author 赵彦博
 * @Date 2024/12/27 14:09
 */
@Component
@Slf4j
public class StpInterfaceImpl implements StpInterface {

    // redis
    @Autowired
    private RedisService redisService;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private RedissonLockService redissonLockService;

    // 获取身份列表
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        Long userId = (Long) loginId;
        // 随机写缓存的过期时间(真正需要写的数据) 缓存过期时间分散化(处理缓存雪崩)
        Long newWriteCacheTime = RandomTimeUtils.randomTime(CacheConstants.CHECK_ROLE_TIME, TimeUnit.MINUTES);
        // 随机写缓存的过期时间(脏数据)  缓存过期时间分散化(处理缓存雪崩)
        Long nullCacheTime = RandomTimeUtils.randomTime(CacheConstants.CHECK_EXP_TIME, TimeUnit.MINUTES);
        try {
            List<String> cacheObjectList = redisService.getCacheObjectList(getAuthRoleKey(userId), String.class);
            if (CollectionUtil.isEmpty(cacheObjectList)) {
                // 获取分布式锁，防止缓存击穿，获取到锁对象
                RLock lock = redissonLockService.lock(CacheConstants.USER_LOCK_KEY + userId.toString());
                while (lock == null) {
                    // 如果获取锁失败，等待一段时间后再次尝试获取锁
                    Thread.sleep(100);
                    lock = redissonLockService.lock(CacheConstants.USER_LOCK_KEY + userId.toString());
                }
                try {
                    // 再次检查缓存，防止其他线程已经更新了缓存
                    cacheObjectList = redisService.getCacheObjectList(getAuthRoleKey(userId), String.class);
                    if (CollectionUtil.isNotEmpty(cacheObjectList)) {
                        return cacheObjectList;
                    }
                    // 数据库查
                    StringBuilder sqlBuilder = new StringBuilder();
                    sqlBuilder.append("select tr.name ")
                            .append("from tb_user_role tur ")
                            .append("join tb_role tr on tr.id = tur.role_id ")
                            .append("where tur.user_id  =? ");
                    cacheObjectList = jdbcTemplate.queryForList(sqlBuilder.toString(), String.class, userId);
                    retryWriteCache(cacheObjectList, userId, newWriteCacheTime, nullCacheTime);
                } finally {
                    // 使用获取到的锁对象进行解锁
                    redissonLockService.unlock(lock);
                }
            }
            return cacheObjectList;
        } catch (Exception e) {
            log.error("查询角色出错" + e);
            return Collections.emptyList();
        }
    }

    // 获取权限列表
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        Long userId = (Long) loginId;
        // 随机写缓存的过期时间(真正需要写的数据) 缓存过期时间分散化(处理缓存雪崩)
        Long newWriteCacheTime = RandomTimeUtils.randomTime(CacheConstants.CHECK_PERMISSION_TIME, TimeUnit.MINUTES);
        // 随机写缓存的过期时间(脏数据)  缓存过期时间分散化(处理缓存雪崩)
        Long nullCacheTime = RandomTimeUtils.randomTime(CacheConstants.CHECK_EXP_TIME, TimeUnit.MINUTES);
        try {
            List<String> cacheObjectList = redisService.getCacheObjectList(getAuthPermissionKey(userId), String.class);
            if (CollectionUtil.isEmpty(cacheObjectList)) {
                // 获取分布式锁，防止缓存击穿，获取到锁对象
                RLock lock = redissonLockService.lock(CacheConstants.USER_LOCK_KEY + userId.toString());
                while (lock == null) {
                    // 如果获取锁失败，等待一段时间后再次尝试获取锁
                    Thread.sleep(100);
                    lock = redissonLockService.lock(CacheConstants.USER_LOCK_KEY + userId.toString());
                }
                try {
                    // 再次检查缓存，防止其他线程已经更新了缓存
                    cacheObjectList = redisService.getCacheObjectList(getAuthPermissionKey(userId), String.class);
                    if (CollectionUtil.isEmpty(cacheObjectList)) {
                        // 数据库查
                        StringBuilder sqlBuilder = new StringBuilder();
                        sqlBuilder.append("select tp.name ")
                                .append("from tb_user_role tur ")
                                .append("join tb_role_permission trp on tur.role_id = trp.role_id ")
                                .append("join tb_permission tp on tp.id = trp.permission_id ")
                                .append("where tur.user_id =? ");
                        cacheObjectList = jdbcTemplate.queryForList(sqlBuilder.toString(), String.class, userId);
                        retryWriteCache(cacheObjectList, userId, newWriteCacheTime, nullCacheTime);
                    }
                } finally {
                    // 使用获取到的锁对象进行解锁
                    redissonLockService.unlock(lock);
                }
            }
            return cacheObjectList;
        } catch (Exception e) {
            log.error("查询权限出错:" + e);
            return Collections.emptyList();
        }
    }

    /**
     * 写缓存多次重试
     * @param cacheObjectList 数据
     * @param userId 用户ID
     * @param newWriteCacheTime 随机写缓存的过期时间(真正需要写的数据) 缓存过期时间分散化(处理缓存雪崩)
     * @param nullCacheTime 随机写缓存的过期时间(脏数据)  缓存过期时间分散化(处理缓存雪崩)
     */
    private void retryWriteCache(List<String> cacheObjectList, Long userId, Long newWriteCacheTime, Long nullCacheTime) {
        // 写缓存
        Integer retryCount = CacheConstants.RETRY_COUNT; // 设置重试次数
        if (CollectionUtil.isNotEmpty(cacheObjectList)) {
            String list = cacheObjectList.toString();
            boolean success = false;
            while (retryCount > 0 && !success) {
                try {
                    redisService.setCacheObject(getAuthRoleKey(userId), list, newWriteCacheTime, TimeUnit.MINUTES);
                    success = true;
                } catch (Exception e) {
                    log.error("缓存更新失败，进行重试，剩余重试次数：{}", retryCount, e);
                    retryCount--;
                    try {
                        Thread.sleep(100); // 每次重试间隔一段时间，避免过于频繁请求Redis
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                        log.error("等待重试时线程被中断", ex);
                        break;
                    }
                }
            }
            if (!success) {
                log.error("经过多次重试，缓存更新仍失败，可能导致数据不一致");
            }
        } else {
            // 解决缓存穿透问题 大量请求 访问缓存和数据库都没有 设置空值
            // 如果数据库也没查到值，设置空值缓存，过期时间设为1分钟
            String nullValue = CacheConstants.CHECK_EXP_VALUE;
            boolean success = false;
            while (retryCount > 0 && !success) {
                try {
                    redisService.setCacheObject(getAuthRoleKey(userId), nullValue, nullCacheTime, TimeUnit.MINUTES);
                    success = true;
                } catch (Exception e) {
                    log.error("缓存更新失败，进行重试，剩余重试次数：{}", retryCount, e);
                    retryCount--;
                    try {
                        Thread.sleep(100); // 每次重试间隔一段时间，避免过于频繁请求Redis
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                        log.error("等待重试时线程被中断", ex);
                        break;
                    }
                }
            }
            if (!success) {
                log.error("经过多次重试，缓存更新仍失败，可能导致数据不一致");
            }
        }
    }

    private String getAuthRoleKey(Long userId) {
        return CacheConstants.AUTH_ROLE_KEY + userId;
    }

    private String getAuthPermissionKey(Long userId) {
        return CacheConstants.AUTH_PERMISSION_KEY + userId;
    }
}
